import java.util.ArrayList;
import java.util.List;


public class PokerGame {
	
public Player getWinner(ArrayList<Player> players) {
		
		Player currentWinner = players.get(0);
		List<Player> ties = new ArrayList<Player>();
		
		for (int challenger = 1; challenger < players.size(); challenger++) {
			int winningHand = evaluateHand(currentWinner).handValue();
			int challengeHand = evaluateHand(players.get(challenger)).handValue();
			
			if (challengeHand > winningHand) {
				currentWinner = players.get(challenger);
			}
			
			else if (challengeHand == winningHand) {
				ties.add(players.get(challenger));
				ties.add(currentWinner);
			}
		}
		
		if (ties.size() > 0) {
			
			Player tieWinner = ties.get(0);
			
			for (Player tieChallenger : ties) {
				
				int winningHand = getHighCard(tieWinner.getHand()).value();
				int challengeHand = getHighCard(tieChallenger.getHand()).value();
				
				if (challengeHand > winningHand) {
					tieWinner = tieChallenger;
				}
			}
			
			return tieWinner;
		}
		
		return currentWinner;
	}
	
	public TypeOfHand evaluateHand(Player player) {
		
		List<Card> hand = player.getHand();
		
		if (isPair(hand)) {
			
			if (isThree(hand)) {
				
				if (isFour(hand)) {
					return TypeOfHand.FOUR_OF_A_KIND;
				}
				
				if (isFullHouse(hand)) {
					return TypeOfHand.FULL_HOUSE;
				}
				
				return TypeOfHand.THREE_OF_A_KIND;
			}
			
			if (isTwoPair(hand)) {
				return TypeOfHand.TWO_PAIR;
			}
			
			return TypeOfHand.PAIR;
		}
		
		if (isFlush(hand)) {
			
			if (isStraight(hand)) {
				
				for (Card card1 : hand) {
					if (card1.face() == Face.ACE) {
						
						for (Card card2 : hand) {
							if (card2.face() == Face.KING) {
								
								return TypeOfHand.ROYAL_FLUSH;
							}
						}
					}
				}
			}
			return TypeOfHand.FLUSH;
		}
		
		if (isStraight(hand)) {
			return TypeOfHand.STRAIGHT;
		}
		
		return TypeOfHand.NOTHING;
	}
	
	/**
	 * Locates the highest card in a given hand
	 * @param hand A List of Cards
	 * @return The highest card in the hand
	 */
	public Card getHighCard (List<Card> hand) {
		
		Card high = hand.get(0);
		
		if (high.face() == Face.ACE) {
			return high;
		}

		for (Card checkCard : hand) {
			
			if (checkCard.face() == Face.ACE) {
				return checkCard;
			}
			
			if (checkCard.value() > high.value()) {
				high = checkCard;
			}
		}
		
		return high;
	}
	
	public Card getLowest(List<Card> hand) {
		
		Card low = hand.get(0);
		
		for (Card checkCard : hand) {
			
			if (checkCard.compareTo(low) < 0) {
				low = checkCard;
			}
		}
		
		return low;
	}
	
	public boolean hasStraightOfFour(List<Card> hand) {
		
		sortHandByValue(hand);
		
		// Special circumstance for having an Ace as the low card
		if (hand.get(4).face() == Face.ACE) {
			if (hand.get(0).face() == Face.TWO) {
				if (hand.get(1).face() == Face.THREE) {
					if (hand.get(2).face() == Face.FOUR) {
						hand.add(0, hand.remove(3));
						return true;
					}
				}
			}
		}
		
		if (hand.get(4).compareTo(hand.get(1)) == 3) {
			// non matching card is already in the first position so just return true
			return true;
		}
		
		if (hand.get(3).compareTo(hand.get(0)) == 3) {
			// move non matching card to the front so the straight is the last 4 cards
			hand.add(0, hand.remove(4));
			return true;
		}
		
		return false;
	}
	
	/**
	 * Counts the cards of each suit in a given hand and
	 * moves all cards of the suit with the highest count to the end
	 * @param hand A List of Cards
	 * @return The number of cards of the suit with the most cards
	 * represented in the hand
	 */
	public int getCountOfSameSuit(List<Card> hand) {
		
		int clubCount = 0;
		int diamondCount = 0;
		int heartCount = 0;
		int spadeCount = 0;
		
		Suit highSuit = Suit.CLUBS;
		
		for (Card card : hand) {
			switch (card.suit()) {
			case CLUBS: {
				clubCount++;
				break;
			}
			case DIAMONDS: {
				diamondCount++;
				break;
			}
			case HEARTS: {
				heartCount++;
				break;
			}
			case SPADES: {
				spadeCount++;
			}
			}
		}
		
		int highest = clubCount;
		if (diamondCount > highest) {
			highest = diamondCount;
			highSuit = Suit.DIAMONDS;
		}
		if (heartCount > highest) {
			highest = heartCount;
			highSuit = Suit.HEARTS;
		}
		if (spadeCount > highest) {
			highest = spadeCount;
			highSuit = Suit.SPADES;
		}
		
		for (int cardPosition = 0; cardPosition < hand.size(); cardPosition++) {
			
			if (hand.get(cardPosition).suit() == highSuit) {
				hand.add(hand.remove(cardPosition));
			}
		}

		return highest;
	}
	
	/**
	 * Checks if a hand contains a pair, and if true, places the pair at the end of the hand
	 * @param hand A List of Cards
	 * @return True if the hand contains a pair, False if not
	 */
	public boolean isPair(List<Card> hand) {
		
		for (int i = 0; i < hand.size(); i++) {
			for (int j = i+1; j < hand.size(); j++) {
				
				if (hand.get(i).value() == hand.get(j).value()) {
					
					// place pair at end of hand
					Card pair1 = hand.remove(j);
					Card pair2 = hand.remove(i);
					hand.add(pair1);
					hand.add(pair2);
					return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Checks a hand that contains a pair to see if it also contains a second pair
	 * @param hand A List of Cards that contains a pair
	 * @return True if there are two pairs, False if there are not
	 */
	public boolean isTwoPair(List<Card> hand) {
		
		if (isPair(hand)) {
			for (int i = 0; i < 2; i++) {
				for (int j = i+1; j < 3; j++) {
					if (hand.get(i).compareTo(hand.get(j)) == 0) {
						Card pair3 = hand.remove(j);
						Card pair4 = hand.remove(i);
						hand.add(pair3);
						hand.add(pair4);
						return true;
					}
				}
			}
			
			return false;
		}
		
		System.out.println("INVALID CARD CONFIGURATION");
		return false;
	}
	
	/**
	 * Checks a hand that contains a pair to see if it also contains a matching third card
	 * @param hand A List of Cards that contains a pair
	 * @return True if there is Three of a Kind, False if there is not
	 */
	public boolean isThree(List<Card> hand) {
		
		if (isPair(hand)) {
			Card match = hand.get(3);
			for (int i = 0; i < 3; i++) {
				if (hand.get(i).compareTo(match) == 0) {
				
					// place third match at the end of the hand
					Card triple = hand.remove(i);
					hand.add(triple);
					return true;
				}
			}
		
			return false;
		}
		
		System.out.println("INVALID CARD CONFIGURATION");
		return false;
	}
	
	/**
	 * Checks a hand that contains three of a kind to see if it also contains a matching 4th card
	 * @param hand A List of Cards that contains three of a kind
	 * @return True if there is a Four of a Kind, False if there is not
	 */
	public boolean isFour(List<Card> hand) {
		
		if (isThree(hand)) {
			Card match = hand.get(2);
			for (int i = 0; i < 2; i++) {
				if (hand.get(i).compareTo(match) == 0) {
					
					// place 4th match at the end of the hand
					Card quad = hand.remove(i);
					hand.add(quad);
					return true;
				}
			}
			
			return false;
		}
		
		System.out.println("INVALID CARD CONFIGURATION");
		return false;
	}
	
	/**
	 * Checks a hand that contains three of a kind to see if it also contains an additional pair
	 * @param hand A List of Cards that contains three of a kind
	 * @return True if there is a Full House, False if there is not
	 */
	public boolean isFullHouse(List<Card> hand) {
		
		if (isThree(hand)) {
			if (hand.get(0).compareTo(hand.get(1)) == 0) {
				return true;
			}
			
			return false;
		}
		
		System.out.println("INVALID CARD CONFIGURATION");
		return false;
	}
	
	public boolean isFlush(List<Card> hand) {
		
		for (int i = 0; i < hand.size(); i++) {
			for (int j = i+1; j < hand.size(); j++) {
				
				if (hand.get(i).suit().equals(hand.get(j).suit()) == false) {
					
					return false;
				}
			}
		}
		
		return true;
	}
	 
	public boolean isStraight(List<Card> hand) {
		
		hand = sortHandByValue(hand);
		
		// Special circumstance for Ace as low card of straight
		if (hand.get(4).value() == 14) {
			if (hand.get(3).value() == 5) {
				if (hand.get(2).value() == 4) {
					if (hand.get(1).value() == 3) {
						if (hand.get(0).value() == 2) {
							return true;
						}
					}
				}
			}
		}
		
		// If highest and lowest card values aren't 4 apart then it is not a straight
		if (hand.get(4).compareTo(hand.get(0)) != 4) {
			return false;
		}
		
		return true;
	}
	
	// Insertion sort selected because the data set is guaranteed to be only 5
	public List<Card> sortHandByValue(List<Card> hand) {
			
		for (int i = 1; i < hand.size(); i++) {
				
			hand = insert(i, hand);
		}
			
		return hand;
	}
		
	private static List<Card> insert(int sortedIndex, List<Card> hand) {
			
		for (int j = sortedIndex - 1; j >= 0; j--) {
				
			if (hand.get(j).value() > hand.get(sortedIndex).value()) {
					
				Card temp = hand.remove(sortedIndex);
				hand.add(j, temp);
				sortedIndex--;
			}
		}
			
		return hand;
	}
}
